// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: lucasyoo

#include "coin2/strategy/hamm/hamm_aggressive_controller.h"

#include "coin2/strategy/hamm/util.h"

namespace coin2::strategy::hamm {

AggressiveController::AggressiveController(
    const AggressiveControllerConfig& agg_config,
    LayeringExecutor2* layer_exec,
    int64_t order_update_period_sec)
    : layer_exec_(layer_exec),
      cooldown_period_(agg_config.cooldown_period_sec() * 1e9),
      order_update_period_(order_update_period_sec * 1e9),
      sw_quote_thold_(agg_config.has_sw_quote_thold() ? agg_config.sw_quote_thold() : 0.0),
      lw_quote_thold_(agg_config.has_lw_quote_thold() ? agg_config.lw_quote_thold() : 0.0),
      update_intv_(agg_config.update_intv_sec() * 1e9),
      midp_ret_thold_(agg_config.midp_ret_thold_bp() * 1e-4),
      vol_thold_(agg_config.vol_thold()),
      skip_quote_thold_(agg_config.has_skip_quote_thold() ? agg_config.skip_quote_thold() : 0.0),
      skip_bid_thold_(agg_config.skip_bid_thold_bp() * 1e-4),
      max_consecutive_skip_(agg_config.max_consecutive_skip()),
      pricing_diff_thold_(agg_config.pricing_diff_thold_bp() * 1e-4),
      midp_(agg_config.midp_window_sec() * 1e9, 1e9),
      vol_(agg_config.vol_window_sec() * 1e9, 1e9),
      sw_fill_(agg_config.sw_fill_sec() * 1e9),
      lw_fill_(agg_config.lw_fill_sec() * 1e9) {
  CHECK_GT(skip_quote_thold_, 0.0);
  CHECK_GT(sw_quote_thold_ + lw_quote_thold_, 0.0);
}

void AggressiveController::Reset() {
  sw_fill_.clear();
  lw_fill_.clear();
}

void AggressiveController::UpdateData(
    int64_t ts,
    double volatility,
    std::pair<double, double> bbo,
    std::pair<double, double> fair_bbo) {
  fair_bbo_ = fair_bbo;
  bbo_ = bbo;
  if (last_update_ts_ + update_intv_ <= ts) {
    midp_.Update(ts, (bbo_.first + bbo_.second) / 2.0);
    vol_.Update(ts, volatility);
    last_update_ts_ = ts;
  }
}

bool AggressiveController::IsOrderbookUnbalanced() const {
  double diff = bbo_.first / ((fair_bbo_.first)) - 1.0;
  SPDLOG_INFO_EVERY_NS(
      60e9,
      ts_,
      fmt::format(
          "IsOrderbookUnbalanced: {}, diff: {:.4f}, pricing_diff_thold: {:.4f}",
          (diff > pricing_diff_thold_),
          diff,
          pricing_diff_thold_));
  return diff > pricing_diff_thold_;
}

bool AggressiveController::IsReversionRegime() const {
  auto midp_first = midp_.first_element();
  auto midp_last = midp_.last_element();
  auto vol_last = vol_.last_element();
  if (!midp_.popped()) return false;
  auto ret = (midp_last - midp_first) / midp_first;
  SPDLOG_INFO_EVERY_NS(
      60e9,
      ts_,
      fmt::format(
          "IsReversionRegime: {}, midp_first: {:.8f}, midp_last: {:.8f}, midp_ret: {:.4f}, vol: "
          "{:.4f}, "
          "midp_ret_thold: {:.4f}, vol_thold: {:.4f}",
          (vol_last > vol_thold_ && ret > midp_ret_thold_),
          midp_first,
          midp_last,
          ret,
          vol_last,
          midp_ret_thold_,
          vol_thold_));
  return (vol_last > vol_thold_ && ret > midp_ret_thold_);
}

std::pair<double, double> AggressiveController::GetOrderFromBook(const FeedUpdate& upd) {
  const auto* book = upd.GetBookBuilder();
  const int bid_size = static_cast<int>(book->BidSize());
  const int step = std::min(bid_size, max_consecutive_skip_);
  double cumul_qty{0.0};
  double cumul_quote{0.0};
  std::pair<double, double> order{0.0, 0.0};
  double price{0.0}, best_bid{0.0}, best_qty{0.0};
  for (int i = 0; i < step; ++i) {
    const auto& bid = book->BidN(i);
    price = bid->price;
    if (i == 0) {
      best_bid = price;
      best_qty = bid->qty;
    }
    if ((cumul_quote + bid->qty * price >= std::min({skip_quote_thold_,
                                                     sw_quote_thold_ - sw_fill_.sum(),
                                                     lw_quote_thold_ - lw_fill_.sum()})) ||
        (price < (fair_bbo_.first + fair_bbo_.second) / 2.0)) {
      break;
    }
    cumul_qty += bid->qty;
    cumul_quote += bid->qty * price;
    LOG(INFO) << "Passing Bid Order: " << bid->qty << " @ " << price;
  }
  if (best_bid == 0.0) return order;
  order = {price, best_qty};
  if (best_bid / order.first - 1.0 < skip_bid_thold_) {
    LOG(INFO) << "Best bid: " << best_bid << " order: " << order.first << " Inside skip_bid_thold";
    order.second = 0.0;
  }
  order.first = best_bid;
  return order;
}

void AggressiveController::ManageAggOrders(const FeedUpdate& upd) {
  ts_ = upd.timestamp();
  if (ts_ - last_cooldown_ts_ <= cooldown_period_) {
    SPDLOG_INFO_EVERY_NS(
        60e9,
        ts_,
        fmt::format(
            "AGG COOLDOWN (TIME_LEFT: {:.2f} SEC)",
            (cooldown_period_ - (ts_ - last_cooldown_ts_)) / 1e9));
    return;
  }
  if (ts_ - last_agg_ts_ > order_update_period_ && IsReversionRegime() &&
      IsOrderbookUnbalanced()) {  // outside order_update_period_ -> Check Condition to make
                                  // aggression
    const auto& [price, qty] = GetOrderFromBook(upd);  // get orders from book
    double curr_pos = layer_exec_->GetPosition();
    const auto& tick_qty = layer_exec_->product_info().tick_qty();
    const auto rounded_qty = tick_qty.Round(qty);
    if (curr_pos > rounded_qty && rounded_qty >= tick_qty.submittable_min_qty(price)) {
      OrderSpec order_spec(
          layer_exec_->product(),
          OrderType::LIMIT_ORDER,
          OrderSide::SELL_ORDER,
          OrderDuration::IOC_ORDER,
          price,
          qty,
          ts_,
          false,
          layer_exec_->config().leverage(),
          layer_exec_->config().filter_tag());
      layer_exec_->os()->gateway()->SubmitOrder(order_spec);
      LOG(INFO) << "TAKER " << upd.product().exchange_name() << " " << upd.product().absolute_norm()
                << " " << qty << " @ " << price;
      sw_fill_.Update(ts_, qty * price);
      lw_fill_.Update(ts_, qty * price);
      last_agg_ts_ = ts_;
    }
    if (sw_quote_thold_ - sw_fill_.sum() < tick_qty.submittable_min_qty(price) ||
        lw_quote_thold_ - lw_fill_.sum() <
            tick_qty.submittable_min_qty(price)) {  // above Threshold -> Reset and Cooldown (Reset)
      last_cooldown_ts_ = ts_;
      last_agg_ts_ = ts_;
      Reset();
    } else if (ts_ - last_agg_ts_ > cooldown_period_) {
      last_cooldown_ts_ = last_agg_ts_;
      Reset();
    } else {
      return;
    }
    SPDLOG_INFO_EVERY_NS(60e9, ts_, "RESET. START COOLDOWN");
  }
}

}  // namespace coin2::strategy::hamm
